//
// ArtHeistController --
//   2009, Tod E. Kurt, http://todbot.com/blog/  & labs.thingm.com
//   For Machine Project November 2009 Fundraiser
//
// Desired:
// - 32 sensor inputs
// - indicator for each sensor  (LCD?)
// - button inputs for control
// - functions:
//    - mode select
//    - alarm reset
//    - alarm trigger
//    - sensor mute
// 
// Possible Hardware:
// - Arduino MEGA has : 54 digital I/O pins, 16 analog in pins
// - use 4 x CD4021 to give 32 digital inputs using 3 Arduino digital I/O
// ==>  going with CD4021 solution
//
// Sensor Types:
// - norm sensors : photocells with lasers constantly illuminating them
// - addl sensors : photocells with lasers triggered by spec sensors
// - spec sensors : switches and such that turn on addl sensors
//
// Alarm Mode:
// - continuously poll sensors for a sensor trip
// - if sensor trip, increase alert level
// - indicate alert level via multi LEDs? 
// - if alert level > max alert level, alarm
// - if special sensors tripped, turn on extra laser
// - press button to reset alert level and silence alarm
//
// Setup Mode:
// - 
// - 
// -
// 
// Sensor Operation:
// -----------------
// Every sensor is digital (HIGH/LOW). 
// Photocells are turned into digital sensors by making a voltage divider
// with a 10k resistor that registers definitely LOW when illuminated by
// a strong light and definitely HIGH when not illuminated.
// Electrically, the photocell is connected between a digital input and Gnd.
// The 10k resistor is connected betwen the digital input and +5V.
// 
// Non-photocell sensors, like switches and buttons, can be wired up 
// in a similar manner.
//
//
// The binary printout format is of the form:
// D-block  C-block  B-block  A-block
// 76543210,76543210,76543210,76543210
// bit position:
// 10987654,32109876,54321098,76543210
// 33222222,22221111,11111100,00000000



const int debug = 1;

const int delayMillis = 500;  // time between loop runs

// what pins on Arduino we are using
const int alarmPin      = 13;   // L5
const int alertPin      = 12;   // L4

const int alert3Pin     = 11;   // L3
const int alert2Pin     = 10;   // L2
const int alert1Pin     = 9;    // L1

const int speakerPin    = 8;

const int regDataPin    = 7;    // CD4021 data pin 
const int regLatchPin   = 6;    // CD4021 latch pin
const int regClockPin   = 5;    // CD4021 clock pin


// All sensors are digital (HIGH/LOW), stored one-per-bit in an 
// array of 8-bit bytes.  Some sensors are "special", most are normal
// (For this particular implementation, could have stored all sensors in a ulong
// or similar and used bit-mask operations. Instead, for didactic purposes,
// arrays and for-loops over those arrays are done)
//const byte sensorRegCount = 1; // how many 8-bit registers of sensor data 
const byte sensorRegCount = 4; // how many 8-bit registers of sensor data 
byte sensorData[ sensorRegCount ];

// sensors (by bit position in sensorData) that are additional-normal sensor
const byte addlSensors[] = { 7 };  // which sensors 
const byte addlSensorCount = sizeof(addlSensors) / sizeof(byte);

// sensors (by bit position) in sensorData which are not normal laser sensors
// this is primarily the pressure sensor under the art piece
const byte speshSensors[] = { 16 };  // which sensors, like pressure sensor 
const byte speshSensorCount = sizeof(speshSensors) / sizeof(byte);

const byte alarmResetSensor = 22;  // button to reset the alarm
const byte alertIncSensor   = 21;  // button to increment the alert level

// polarity of a sensor bit when it is triggered
// (e.g.HIGH if no beam detected, LOW if beam present for our photocell sensors)
const byte sensorPolarity = HIGH;  // look for this case to mean sensor trigger

// on startup record which sensors are not used or bad
byte badSensors[sensorRegCount];


// different modes (states) in the controller
enum { 
    SETUP,
    RUN,          // normal operation
    ALERT,        // if a sensor is triggered, it starts getting antsy
    ALARM,        // full on alarm 
};
byte mode;

byte useAddlSensors;  // if special sensors are tripped, use additional sensors

byte alertLevel;
int alarm = 0;

int sensorsTripped;           // how many sensors tripped (in checkSensors)
int speshSensorsTripped;       // how many special sensors tripped

void(* softReset) (void) = 0;  //declare reset function @ address 0

//
void setup()
{ 
    Serial.begin( 19200 );
    
    pinMode( alarmPin,    OUTPUT);
    pinMode( alertPin,    OUTPUT);

    pinMode( alert3Pin,   OUTPUT);
    pinMode( alert2Pin,   OUTPUT);
    pinMode( alert1Pin,   OUTPUT);

    pinMode( speakerPin,  OUTPUT);

    pinMode( regLatchPin, OUTPUT);
    pinMode( regClockPin, OUTPUT); 
    pinMode( regDataPin,   INPUT);
    

    digitalWrite( alarmPin, LOW);  // turn off alarm and alert
    digitalWrite( alertPin, LOW);

    digitalWrite( alert3Pin, LOW); 
    digitalWrite( alert2Pin, LOW); 
    digitalWrite( alert1Pin, LOW); 

    digitalWrite( speakerPin, LOW);

    // debug, or maybe for show
    playTickSound();
    /*
    digitalWrite( alertPin, HIGH);
    delay(500);
    digitalWrite( alertPin, LOW);
    delay(500);
    digitalWrite( alertPin, HIGH);
    delay(500);
    digitalWrite( alertPin, LOW);
    */
    Serial.println("ArtHeistController ready!");
    resetAlarm();
    mode = RUN;

    readSensors();
    // save bad sensors
    memcpy( badSensors, sensorData, sensorRegCount);
}

//
void loop()
{
    readSensors();
    checkSensors();  // sets 'sensorsTripped' and 'specialSensorsTripped'

    if(debug) debugSensors();

    if( sensorsTripped ) { 
        Serial.println("SENSOR HIT!");
        incrementAlertLevel();
    }
    
    if( speshSensorsTripped ) {
        Serial.println("SPECIAL SENSOR HIT!");
        playTickSound();
        //enableLaser( 1, HIGH); // turn on 2nd laser battery
        //useAddlSensors = 1;    // enable reading those additional sensors
    }
    
    // reset entire deal
    if( checkButtonPress( alarmResetSensor, LOW ) ) {   // active LOW
        resetAlarm();
        softReset();
    }

    if( checkButtonPress( alertIncSensor, LOW) ) { // active LOW
        incrementAlertLevel();
    }

    if( mode == ALARM ) {  // FIXME: this is wrong
        alarm = 1;
    }

    updateAlertLevel();  // sets alert LEDs to mirror state
    updateAlarm();       // sets alarm output to mirror state

    delay( delayMillis );
}

/**
 *
 */
void resetAlarm()
{
    Serial.println("resetAlarm");

    alertLevel = 0;
    useAddlSensors = 0;
    alarm = 0;

    setAlert(LOW);
    //enableLaser( 1, HIGH);
    //enableLaser( 2, LOW);

    updateAlertLevel();
    updateAlarm();
    mode = RUN;
}

/**
 *
 */
void setAlert(int val)
{
    digitalWrite( alertPin, val);
    //incrementAlertLevel();  // don't want to go to alarm yet tho
}

/**
 *
 */
void incrementAlertLevel() 
{
    //playTone( 1000, 100);
    playTickSound();
    if( mode == RUN ) {  // FIXME: what about SETUP?
        mode = ALERT;
    }
    alertLevel++;
    if( alertLevel > 3 ) { 
        alertLevel = 3;
        mode = ALARM;    // uh oh, now you've done it
    }
}


/**
 * Updates LEDs to match state of "alertLevel" global
 */
void updateAlertLevel()
{
    if( alertLevel == 0 ) {
        digitalWrite( alert1Pin, LOW );
        digitalWrite( alert2Pin, LOW );
        digitalWrite( alert3Pin, LOW );
    }
    else if( alertLevel == 1 ) {
        digitalWrite( alert1Pin, HIGH );
        digitalWrite( alert2Pin, LOW );
        digitalWrite( alert3Pin, LOW );
    }
    else if( alertLevel == 2 ) {
        digitalWrite( alert1Pin, HIGH );
        digitalWrite( alert2Pin, HIGH );
        digitalWrite( alert3Pin, LOW );
    }
    else if( alertLevel == 3 ) {
        digitalWrite( alert1Pin, HIGH );
        digitalWrite( alert2Pin, HIGH );
        digitalWrite( alert3Pin, HIGH );
    }
}

/**
 * Update the alarm outpt based on 'alarm' global variable
 * The alarm is a set of American DJ police beacons that have been modded
 * with a transistor switch.  
 * When that switch is HIGH, the lights turn OFF
 * When that switch is LOW,  the lights turn ON
 */
void updateAlarm()
{
    if( alarm ) {
        digitalWrite(alarmPin, HIGH);  // enables alarm
        playTickSound();
    } 
    else {
        digitalWrite(alarmPin, LOW); // disables alarm
    }
}


/**
 * Print out the state of all the sensors
 * (includes those register bits not considered sensors)
 *
 * The binary printout format is of the form:
 * D-block  C-block  B-block  A-block
 * 76543210,76543210,76543210,76543210
 * bit position:
 * 10987654,32109876,54321098,76543210
 * 33222222,22221111,11111100,00000000
 */
void debugSensors()
{
    Serial.print("mode:");  
    Serial.print((mode==SETUP?"SETUP":(mode==RUN?"RUN  ":"ALARM")));
    Serial.print(" alertLevel:");         Serial.print(alertLevel,DEC);
    Serial.print(" sensorsTripped:");     Serial.print(sensorsTripped);
    Serial.print(" speshSensorsTripped:"); Serial.println(speshSensorsTripped);

    Serial.print("all sensors:");
    for( int i=0; i<sensorRegCount; i++) {
        printByte( sensorData[sensorRegCount-1-i]);
        Serial.print(",");
    }
    Serial.println();
    Serial.print("bad sensors:");
    for( int i=0; i<sensorRegCount; i++) {
        printByte( badSensors[sensorRegCount-1-i] );
        Serial.print(",");
    }
    Serial.println();

}

/**
 * checks for button presses in sensorData
 * based on sensorNum bit position and button polarity (HIGH,LOW)
 * FIXME: this is really a general 'checkSensor' sort of func
 */
byte checkButtonPress(int sensorNum, int polarity)
{
    byte reg = sensorNum / 8; // which register to look at
    byte pos = sensorNum % 8; // which bit to look at in the reg

    byte val = (sensorData[reg] & (1<<pos)) ? HIGH:LOW;  // get the sensor bit

    return (val == polarity);
}

/**
 * return true (1) if bitnum is a valid sensor 
 * or return false (0) if not valid
 */
byte isNormalSensor(int sensornum) 
{
    for( int i=0; i< speshSensorCount; i++) {
        if( sensornum == speshSensors[i] )
            return 0;
    }

    if( useAddlSensors ) {
        for( int i=0; i< addlSensorCount; i++) {
            if( sensornum == addlSensors[i] ) 
                return 0;
        }
    }
    return 1;
}

/**
 * clears then sets global 'sensorsTripped' if any sensors triggered
 * clears then sets global 'speshSensorsTripped' if any sensors triggered
 */
void checkSensors()
{
    sensorsTripped = 0;
    speshSensorsTripped = 0;
    byte dat,val,badval;
    int sensornum = 0;
    for( int i=0; i<sensorRegCount; i++) {    
        sensornum = i*8;                      
        dat = sensorData[i];                  // get a byte of sensor data
        for( int j=0; j<8; j++) {             // scan thru the byte, bit by bit
            sensornum += j;
            val = (dat & (1<<j)) ? HIGH:LOW;  // get the sensor bit  
            badval = (badSensors[i] & (1<<j)) ? HIGH:LOW;
            if( badval == sensorPolarity )    // if bad sensor
                continue;
            if( val == sensorPolarity ) {     // and test it
                if( isNormalSensor( sensornum ) ) 
                    sensorsTripped++;
                else
                    speshSensorsTripped++;
            }
        }
    }
}

/**
 * Read an array of digital sensors hooked up to one or more 
 * CD4021 shift registers
 * Writes the global variable 'sensorData'
 */
void readSensors()
{
    // Pulse the latch pin:
    digitalWrite( regLatchPin, 1);   // set to 1 to collect parallel data
    delayMicroseconds(20);
    digitalWrite( regLatchPin, 0);   // set to 0 to transmit data serially  
    
    // collect each shift register into a byte
    // the register attached to Arduino comes in first 
    for( int i = 0; i< sensorRegCount; i++ ) {
        sensorData[i] = shiftIn( regDataPin, regClockPin);
    }
}

/**
 * shiftIn function
 * needs the location of the data pin and the clock pin
 * returns a byte with each bit in the byte corresponding
 * to a pin on the shift register. leftBit 7 = Pin 7 / Bit 0= Pin 0
 */
byte shiftIn(int myDataPin, int myClockPin)
{
    int i;
    int temp = 0;
    byte myDataIn = 0;
    
    pinMode(myClockPin, OUTPUT);
    pinMode(myDataPin, INPUT);
    
    //we will be holding the clock pin high 8 times (0,..,7) at the
    //end of each time through the for loop
    
    //at the begining of each loop when we set the clock low, it will
    //be doing the necessary low to high drop to cause the shift
    //register's DataPin to change state based on the value
    //of the next bit in its serial information flow.
    //The register transmits the information about the pins from pin 7 to pin 0
    //so that is why our function counts down
    for (i=7; i>=0; i--) {
        digitalWrite(myClockPin, 0);
        delayMicroseconds(2);
        temp = digitalRead(myDataPin);
        if (temp) {
            myDataIn = myDataIn | (1 << i);  //set the bit to 0 no matter what
        }
        digitalWrite(myClockPin, 1);
    }
    
    return myDataIn;
}


/**
 * enables or disables a laser array
 */
/*
void enableLaser( int lasernum, int onoff )
{
    if(debug) {
        Serial.print( "enableLaser:"); Serial.print(lasernum);
        Serial.print("->"); Serial.println( (onoff ? HIGH:LOW) );
    }
    if( lasernum == 1 ) {
        digitalWrite( laser1Pin, onoff);
    } 
    else if( lasernum == 2 ) {
        digitalWrite( laser2Pin, onoff);
    } 
}
*/

// utilty functions

// print a byte out as a string
// Normally we'd use Serial.print(b,BIN), but it truncates zero upper bits
void printByte(byte b)
{
  for( int i=7; i>=0; i--) {
    Serial.print( ((b&(1<<i)) ? '1':'0') );
  }
}

/**
 * Sound generator hooked up to speakerPin.
 * Setting speakerPin HIGH turns sound on
 */
void playTickSound()
{
    setAlert( HIGH );
    digitalWrite(speakerPin, HIGH);
    delay(100);
    digitalWrite(speakerPin,LOW);
    setAlert( LOW );
}

// old functions

/**
 * uses speakerPin 
 */
void playTone(int tone, int duration)
{
    for (long i = 0; i < duration * 1000L; i += tone * 2) {
        digitalWrite(speakerPin, HIGH);
        delayMicroseconds(tone);
        digitalWrite(speakerPin, LOW);
        delayMicroseconds(tone);
    }
}

/**
 * ledPin was alarm pin
 */
int testAlarm()
{ 
    Serial.println("HIGH");
    digitalWrite( alarmPin, HIGH );
    delay(500);
    Serial.println("LOW");
    digitalWrite( alarmPin, LOW );
    delay(500);
}

